package com.zws.cucumber.util;

import com.zws.cucumber.ZwsUtils;
import com.zws.cucumber.adaptation.ZwsFeignImplementedService;
import feign.AsyncClient;
import feign.ReflectiveAsyncFeign;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Set;
import java.util.concurrent.*;

public class ZwsNotificationUtil {

    private static final String KEY_EVENT_NOTIFICATION = "event-notification";
    private static final String KEY_USER_SECURITY_EVENT = "user-security-event";
    private static final String KEY_CREATE_MEMO_V2 = "create-memo-v2";

    private final static ExecutorService singleExecutorService = Executors.newFixedThreadPool(1);
    private final static Set<String> injectedSet = ConcurrentHashMap.newKeySet(32);
    private static volatile boolean succeedInInjectingLogUserSecurityEventExecutorService = false;

    public static void initThreadPool() {
        // fixme: to be implemented @ZWS
    }

    public static void waitUntilNotificationSent() {
        waitUntilDone(KEY_EVENT_NOTIFICATION);
    }

    public static void initThreadPoolForLogUserSecurityEvent(ZwsFeignImplementedService service) {


        injectIntoFeign(KEY_USER_SECURITY_EVENT, service);
    }

    public static void waitUntilUserSecurityEventSent() {
        waitUntilDone(KEY_USER_SECURITY_EVENT);
    }

    public static void waitUntilCreateMemoV2Sent() {
        waitUntilDone(KEY_CREATE_MEMO_V2);
    }

    private static void injectIntoFeign(String key, Object service) {
        try {
            Object ihOrJdkDynamicAopProxy = Proxy.getInvocationHandler(service);

            Object ih = null;
            if (ihOrJdkDynamicAopProxy instanceof AopProxy) {
                Field fieldOfAdvised = ReflectionUtils.findField(ihOrJdkDynamicAopProxy.getClass(), "advised");
                fieldOfAdvised.setAccessible(true);
                AdvisedSupport advised = (AdvisedSupport) ReflectionUtils.getField(fieldOfAdvised, ihOrJdkDynamicAopProxy);

                ih = Proxy.getInvocationHandler(advised.getTargetSource().getTarget());
            } else {

                ih = ihOrJdkDynamicAopProxy;
            }

            // ReflectiveAsyncFeign
            Field fieldOfReflectiveAsyncFeign = ReflectionUtils.findField(ih.getClass(), "this$0");
            fieldOfReflectiveAsyncFeign.setAccessible(true);
            ReflectiveAsyncFeign feign = (ReflectiveAsyncFeign) ReflectionUtils.getField(fieldOfReflectiveAsyncFeign, ih);

            // AddingMdcHeaderAsyncClientWrapper or AsyncClient$Default (if no need to add logtraceid)
            Field fieldOfClientWrapper = ReflectionUtils.findField(feign.getClass(), "client");
            fieldOfClientWrapper.setAccessible(true);
            AsyncClient clientWrapper = (AsyncClient) ReflectionUtils.getField(fieldOfClientWrapper, feign);
            AsyncClient clientDefault = clientWrapper;

            ZwsUtils.setFieldValue(clientDefault.getClass(), "executorService", clientDefault, singleExecutorService);
            injectedSet.add(key);
        } catch (Throwable t) {
            throw new RuntimeException(t);
        }
    }

    public static void waitUntilDone(String key, long defaultTimeoutMs) {
        if (injectedSet.contains(key)) {
            Future<?> future = singleExecutorService.submit(() -> System.out.printf("[%s] %s was done%n", Thread.currentThread().getName(), key));
            try {
                future.get(30, TimeUnit.SECONDS);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else {
            try {
                Thread.sleep(defaultTimeoutMs != 0 ? defaultTimeoutMs : 1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void waitUntilDone(String key) {
        waitUntilDone(key, 0);
    }

}
